#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS

#ifdef _WIN32
	#include <winsock2.h>
#else
	#include "arpa/inet.h"
	#include "unistd.h"
	#include "string.h"
#endif

#include <stdio.h>
#include <cstdint>
#include <vector>
#include <iterator>

#pragma comment(lib,"ws2_32.lib")

typedef unsigned int SOCKET;

// use select model
// 
// simulate login service

enum class DataType
{
	LOG_IN, LOGIN_OK, LOGIN_FAILED, LOG_OUT,
	PERSON_INFO,
	REQUEST_ERROR
};

// 1 fisrt get response type, then get request data
struct DataTypePackage {
	DataType data_type;
	unsigned int data_length;
};
// login data
struct Login : public DataTypePackage {

	Login() {
		data_type = DataType::LOG_IN;
		data_length = sizeof(Login);

		// may cause local variable mem be clear!!!
		// memset(name, 0, strlen(name));
		// memset(passwd, 0, strlen(passwd));
	}

	char name[128];
	char passwd[128];

};

struct LoginOk : public DataTypePackage {

	LoginOk() {
		data_type = DataType::LOGIN_OK;
		data_length = sizeof(LoginOk);
	}

	char name[128];

};

struct LoginFailed : public DataTypePackage {

	LoginFailed() {
		data_type = DataType::LOGIN_FAILED;
		data_length = sizeof(LoginFailed);
	}

	char msg[128];

};

struct LogOut : public DataTypePackage {

	LogOut() {
		data_type = DataType::LOG_OUT;
		data_length = sizeof(LogOut);
	}

	char name[128];

};

// use struct
struct PersonInfo : public DataTypePackage {

	PersonInfo() {
		data_type = DataType::PERSON_INFO;
		data_length = sizeof(PersonInfo);
		age = 0;
	}

	char name[128];
	unsigned int age;

};

struct RequestError : public DataTypePackage {

	RequestError() {
		data_type = DataType::REQUEST_ERROR;
		data_length = sizeof(RequestError);
	}

	char msg[128];

};

int handle_read_fd(SOCKET);

int main() {

#ifdef _WIN32
	//��ʼ��WSA
	WORD sockVersion = MAKEWORD(2, 2);
	WSADATA wsaData;
	if (WSAStartup(sockVersion, &wsaData) != 0)
	{
		return 0;
	}
#endif
	// 1. ����socket
	SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	sockaddr_in addr = {};
	addr.sin_family = AF_INET;
	addr.sin_port = htons(6666);// ��С��ת��������ʹ�õĴ����
#ifdef _WIN32
	addr.sin_addr.S_un.S_addr = INADDR_ANY;// ����������ip
#else
	addr.sin_addr.s_addr = INADDR_ANY;
#endif
	// 2. bind
	bind(_sock, (sockaddr*)&addr, sizeof(addr));

	// 3. listen
	listen(_sock, 5);

	// 4. init fd set
	fd_set readfd;
	fd_set writefd;
	fd_set exceptfd;

	FD_ZERO(&readfd);
	FD_ZERO(&writefd);
	FD_ZERO(&exceptfd);

	// 5. set fd set
	FD_SET(_sock, &readfd);
	FD_SET(_sock, &writefd);
	FD_SET(_sock, &exceptfd);

	std::vector<SOCKET> _client_list;
	printf("waiting client connect...\n");
	while (true) {

		for (auto c : _client_list) {
			FD_SET(c, &readfd);
		}
		FD_SET(_sock, &readfd);
	
		// ����ʽ��ȡ�ɲ�����fd_set
		printf("wait select\n");
		int n = select(FD_SETSIZE, &readfd, &writefd, &exceptfd, NULL);// ��ΪNULL������������ʽ

		//if (readfd.fd_count > 0) {
			// accept
			// for (unsigned int i = 0; i < readfd.fd_count; i++) {
			// 	if (readfd.fd_array[i] == _sock) {
					if(FD_ISSET(_sock, &readfd)){
						sockaddr_in caddr = {};
						unsigned int caddr_len = sizeof(caddr);
						// 6. accept
						int c = accept(_sock, (sockaddr*)&caddr, &caddr_len);
						printf("connect client ip: %s\n", inet_ntoa(caddr.sin_addr));
						_client_list.push_back(c);
					}
			// 	}
			// }

			// handle read fd
			std::vector<SOCKET>::iterator it = _client_list.begin();
			for (; it != _client_list.end(); ) {
				if (FD_ISSET(*it, &readfd)) {
					printf("handle read fd\n");
					if (handle_read_fd(*it) < 0) {
						SOCKET _r = *it;
						it = _client_list.erase(it);// ����ɾ��λ�õĺ�һ��λ��
						printf("clear\n");
						FD_CLR(_r, &readfd);
					}
					else {
						it++;
					}
				}
				else {
					it++;
				}
			}
		//}
	}
	// 8. close server socket.

#ifdef _WIN32
	closesocket(_sock);
	WSACleanup();
#else
	close(_sock);
#endif

	return 0;
}

int handle_read_fd(SOCKET client) {

	char data[1024] = {};
	// 5. get cmd from client
	int len = recv(client, data, 1024, 0);
	if (len < 1) {
		printf("data_type len < 1\n");
#ifdef _WIN32		
		closesocket(client);
#else
		close(client);
#endif		
		return -1;
	}

	DataTypePackage* dataType;
	dataType = (DataTypePackage*)data;

	switch (dataType->data_type)
	{
	case DataType::LOG_IN: {

		Login* login;
		//char buf[sizeof(Login)];
		// strncpy(buf, &data[sizeof(DataTypePackage)], strlen(buf));

		login = (Login*)data;

		char name[] = "zhangsan";
		char password[] = "zs";

		printf("name: %s, passwd: %s\n", login->name, login->passwd);

		if (strcmp(name, login->name) == 0 && strcmp(password, login->passwd) == 0) {

			// login ok
			LoginOk loginOk = {};
			strcpy(loginOk.name, name);

			send(client, (char*)&loginOk, sizeof(loginOk), 0);

		}
		else {
			// login failed
			// login ok
			LoginFailed loginFailed = {};
			strcmp(loginFailed.msg, "login error");
			send(client, (char*)&loginFailed, sizeof(loginFailed), 0);
		}

	}break;
	case DataType::LOG_OUT: {
		// log out
		printf("client logout\n");
		return -1;
	}break;
	case DataType::PERSON_INFO: {
		// request person info
		PersonInfo person = {};
		strcpy(person.name, "zhangsan");
		person.age = 22;

		send(client, (char*)&person, sizeof(person), 0);
		printf("person_info\n");

	}break;
	default: {
		// invalid data
		RequestError requestError = {};
		strcpy(requestError.msg, "invalid cmd");
		return 0;
		}
	}

	return 0;

}

